package com.xzzz.backend.bsms.server.rbac.sys.handlelog;

import com.xzzz.common.base.util.DateUtils;
import com.xzzz.common.base.util.json.JsonUtil;
import com.xzzz.irda.tracker.core.Tracker;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.util.Objects;
import java.util.concurrent.Executor;


/**
 * 操作记录切面
 *
 * @author xzzz
 */
@Slf4j
@Aspect
@Component
public class HandleLogAspect {

    // spel表达式解析器
    private final SpelExpressionParser spelExpressionParser = new SpelExpressionParser();

    // 参数名发现器
    private final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    @Autowired
    private HandleLogMapper logMapper;

    @Autowired
    @Qualifier("taskExecutor")
    private Executor taskExecutor;


    @Around("@within(handleLog)")
    public Object aroundClass(ProceedingJoinPoint joinPoint, HandleLog handleLog) throws Throwable {
        return around(joinPoint, handleLog);
    }

    @Around("@annotation(handleLog)")
    public Object aroundMethod(ProceedingJoinPoint joinPoint, HandleLog handleLog) throws Throwable {
        return around(joinPoint, handleLog);
    }

    private Object around(ProceedingJoinPoint joinPoint, HandleLog handleLog) throws Throwable {
        HandleLogEntity log = createLog(joinPoint, handleLog);
        Object[] args = joinPoint.getArgs();
        Object returnObj;
        try {
            returnObj = joinPoint.proceed(args);
            saveReturn(log, handleLog, returnObj);
        } catch (Throwable throwable) {
            log.setException("[" + throwable.getClass().getName() + "]" + throwable.getMessage());
            log.setSuccess("FAIL");
            throw throwable;
        } finally {
            log.setEndTime(DateUtils.date());
            taskExecutor.execute(() -> logMapper.insert(log));
        }

        return returnObj;
    }

    /**
     * 创建日志
     *
     * @param joinPoint 切点
     * @param handleLog 注解
     * @return 日志实体
     */
    private HandleLogEntity createLog(ProceedingJoinPoint joinPoint, HandleLog handleLog) {
        HandleLogEntity log = new HandleLogEntity();
        log.setTraceId(Tracker.getTraceId());
        log.setSuccess("SUCC");
        log.setBeginTime(DateUtils.date());
        if (handleLog != null) {
            log.setTag(handleLog.tag());
            log.setType(handleLog.type());
            log.setDesc(expression(handleLog.desc(), (MethodSignature) joinPoint.getSignature(), joinPoint.getArgs()));
        }
        Class<?> clazz = joinPoint.getTarget().getClass();
        String methodName = joinPoint.getSignature().getName();
        log.setClassName(clazz.getSimpleName());
        log.setMethodName(methodName);

        if (handleLog != null && handleLog.saveReturn()) {
            try {
                log.setMethodParam(JsonUtil.toJson(joinPoint.getArgs()));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return log;
    }

    /**
     * 保存响应
     *
     * @param log       日志
     * @param handleLog 注解
     * @param returnObj 响应
     */
    private void saveReturn(HandleLogEntity log, HandleLog handleLog, Object returnObj) {
        if (handleLog != null && handleLog.saveReturn()) {
            try {
                log.setMethodResult(JsonUtil.toJson(returnObj));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private String expression(String desc, MethodSignature method, Object[] args) {
        if (method == null) {
            return desc;
        }
        // Spel表达式解析日志信息
        // 获得方法参数名数组
        try {
            String[] parameterNames = parameterNameDiscoverer.getParameterNames(method.getMethod());
            if (parameterNames != null && parameterNames.length > 0) {
                EvaluationContext context = new StandardEvaluationContext();
                for (int i = 0; i < args.length; i++) {
                    context.setVariable(parameterNames[i], args[i]); // 替换spel里的变量值为实际值， 比如 #user -->  user对象
                }
                // 解析出实际的日志信息
                return Objects.requireNonNull(spelExpressionParser.parseExpression(desc).getValue(context)).toString();
            }
        } catch (Exception e) {
        }

        return desc;
    }
}
